#ifndef LEVEL
#define LEVEL

#include <base\vec2i.h>
#include <render\observers\downloadObserver.h>
#include <render\threads\downloadManager.h>

#include <render\levels\tile.h>
#include <network\streamingClient.h>

#include <app\states\moveState.h>
#include <app\states\rotateState.h>
#include <app\states\viewingParametersState.h>

#include <openglut.h>
#include <GL\glext.h>

#include <boost/thread.hpp>
#include <boost/shared_ptr.hpp>

#include <vector>

using namespace std;

#define PBO_SIZE 1024

class level : public downloadObserver {

  int level_;

  unsigned char *texture_data;
  unsigned char *height_data;

  enum e_level_status {
    CONTINUE,
    STOP,
    DISCARD
  } curr_level_status, to_level_status;

  int keep_discard_state;


private:

  enum e_vbo_desc {
    BOTTOM = 0,
    LEFT   = 1,
    RIGHT  = 2,
    TOP    = 3,
    CENTER = 4
  };

  bool data_changed;
  bool first_render;
  int Lshape;

  vec2i     patch_size;
  int       half_patch_size;
  int       pow_2_level;

private: // vbo and pbo stuff

  // texture stuff
  GLuint    textureId;
  GLuint    texturePboId;
  GLubyte * texData;
  GLubyte * mapped_gpu_data;

  // overall patch vertices
  GLuint    verticesVboId;
  GLfloat * vertices;
  int       vertices_count;

  // overall texture coordinates
  GLuint    texcoordsVboId;
  GLfloat * texcoords;
  int       texcoords_count;

  // overall patch vertices normals
  GLuint    normalsVboId;
  GLfloat * normals;
  int       normals_count;

  // letft, right, top and bottom indices creating triangle strips (or 0th index for stop patch)
  GLuint    indicesVboId[5];
  GLuint *  indices[5];
  int       indices_count[5];

  // indices for left, right, top and bottom L shaped regions around center of each patch
  GLuint    LshapedIndicesVboId[4];
  GLuint *  LshapedIndices[4];
  int       LshapedIndices_count[4];

private:

  viewFrustum    frustum;

private:

  vector<vector<boost::shared_ptr<tile> *> > *all_loaded_tiles;
  vector<boost::shared_ptr<tile> *> *curr_loaded_tiles;

  vec3f to_position;
  vec3f current_position;

  vec2i to_center;
  vec2i current_center;

  vec3f start_position;

private: // states

  viewingParametersState  vp_state;      ///< shared thus maintained from viewResolver
  viewingParametersState *dyn_vp_state;  ///< dynamic update of view frustum
  modelInfoState         *mi_state;
  moveState              *move_state;

private:

  void initVertices();
  void generateTextureCoordinates();
  void createBasePatch();
  void createLShapes();

  boost::shared_ptr<tile> * searchForTile(vec2i tile_id);
  boost::shared_ptr<tile> * searchForTileAndPop(vec2i tile_id);
  void updateLoadedTiles(viewFrustum vf);
  void updateVisibility(viewFrustum vf);

  void constructLevel(viewFrustum vf);
  void updateTextures(GLubyte* dst, int tile_x, int tile_y);
  void fillGPUBuffers();

  unsigned short **getTerrainRows(int tile_x, int tile_y);
  unsigned char **getTextureRows(int tile_x, int tile_y);

public:

  bool prepare(vec3f to_position_, viewFrustum to_vf, bool can_continue, int top_level);
  void render(bool can_upload_gpu_data);

  s_download_query generateDownloadQuery();

  level(int _level, vector<vector<boost::shared_ptr<tile> *> > *all_loaded_tiles_, viewingParametersState *vp_state_);
  ~level();
};

#endif